package com.tianji.promotion.service.impl;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.tianji.common.domain.dto.PageDTO;
import com.tianji.common.exceptions.BadRequestException;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.utils.*;
import com.tianji.promotion.constants.PromotionConstants;
import com.tianji.promotion.domain.dto.CouponFormDTO;
import com.tianji.promotion.domain.dto.CouponIssueFormDTO;
import com.tianji.promotion.domain.po.Coupon;
import com.tianji.promotion.domain.po.CouponScope;
import com.tianji.promotion.domain.po.UserCoupon;
import com.tianji.promotion.domain.query.CouponQuery;
import com.tianji.promotion.domain.vo.CouponPageVO;
import com.tianji.promotion.domain.vo.CouponVO;
import com.tianji.promotion.enums.CouponStatus;
import com.tianji.promotion.enums.ObtainType;
import com.tianji.promotion.enums.UserCouponStatus;
import com.tianji.promotion.mapper.CouponMapper;
import com.tianji.promotion.service.ICouponScopeService;
import com.tianji.promotion.service.ICouponService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.promotion.service.IExchangeCodeService;
import com.tianji.promotion.service.IUserCouponService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.context.Theme;
import org.springframework.web.bind.annotation.RequestBody;
import springfox.documentation.service.ApiListing;

import java.io.DataOutput;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * <p>
 * 优惠券的规则信息 服务实现类
 * </p>
 *
 * @author zzj
 * @since 2024-05-19
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> implements ICouponService {

    private final ICouponScopeService couponScopeService;//优惠券的限定范围业务类
    private final IExchangeCodeService exchangeCodeService;//兑换码的业务类
    private final IUserCouponService userCouponService;
    private final StringRedisTemplate redisTemplate;

    @Override
    @Transactional
    public void saveCoupon(CouponFormDTO dto) {
        //dto转po 保存优惠券 coupon表
        Coupon coupon = BeanUtils.copyBean(dto, Coupon.class);
        this.save(coupon);
        //判断是否限定了范围 dto.specific  如果为false直接return
        if (!dto.getSpecific()){
            return;//说明没有限定优惠券的使用范围
        }
        //如果dto.specific 为true 需要校验dto.scopes
        List<Long> scopes = dto.getScopes();
        if (CollUtils.isEmpty(scopes)){
            throw new BadRequestException("分类id不能为空");
        }
        //保存优惠券的使用范围 coupon_scope 批量更新
//        List<CouponScope> csList = new ArrayList<>();
//        for (Long scope : scopes) {
//            CouponScope couponScope = new CouponScope();
//            couponScope.setCouponId(coupon.getId());
//            couponScope.setBizId(scope);
//            couponScope.setType(1);
//            csList.add(couponScope);
//        }
        List<CouponScope> csList = scopes
                .stream()
                .map(aLong -> new CouponScope().setCouponId(coupon.getId()).setBizId(aLong).setType(1))
                .collect(Collectors.toList());
        couponScopeService.saveBatch(csList);


    }

    @Override
    public PageDTO<CouponPageVO> queryCouponPage(CouponQuery query) {
        //分页条件查询优惠券 Coupon
        Page<Coupon> page = this.lambdaQuery()
                .eq(query.getType() != null, Coupon::getDiscountType, query.getType())
                .eq(query.getStatus() != null, Coupon::getStatus, query.getStatus())
                .eq(StringUtils.isNotBlank(query.getName()), Coupon::getName, query.getName())
                .page(query.toMpPageDefaultSortByCreateTimeDesc());
        List<Coupon> records = page.getRecords();
        if (CollUtils.isEmpty(records)){
            return PageDTO.empty(page);
        }
        //封装vo返回
        List<CouponPageVO> voList = BeanUtils.copyToList(records, CouponPageVO.class);
        return PageDTO.of(page,voList);
    }

    @Override
    public void issueCoupon(Long id, CouponIssueFormDTO dto) {
        log.debug("发放优惠券 线程名：{}",Thread.currentThread().getName());
        //校验
        if (id == null || !id.equals(dto.getId())){
            throw new BadRequestException("非法参数");
        }
        //校验优惠券id是否存在
        Coupon coupon = this.getById(id);
        if (coupon == null){
            throw new BadRequestException("优惠券不存在");
        }

        //校验优惠券状态 只有待发放和暂停状态才能发放
        if (coupon.getStatus() != CouponStatus.DRAFT && coupon.getStatus() != CouponStatus.PAUSE){
            throw new BizIllegalException("只有待发放和暂停中的优惠券才能发放");
        }
        LocalDateTime now = LocalDateTime.now();
        //该变量代表优惠券是否立即发放
        boolean isBeginIssue = dto.getIssueBeginTime() == null || !dto.getIssueBeginTime().isAfter(now);
        //修改优惠券的 领取开始和结束日期 使用有效期开始和结束日 天数 状态

        //方式1
//        if (isBeginIssue){
//            coupon.setIssueBeginTime(dto.getIssueBeginTime() == null ? now : dto.getIssueBeginTime());
//            coupon.setIssueEndTime(dto.getIssueEndTime());
//            coupon.setStatus(CouponStatus.ISSUING);//如果是立即发放，优惠券状态需要改为进行中
//            coupon.setTermDays(dto.getTermDays());
//            coupon.setTermBeginTime(dto.getTermBeginTime());
//            coupon.setTermEndTime(dto.getTermEndTime());
//        }else {
//            coupon.setIssueBeginTime(dto.getIssueBeginTime());
//            coupon.setIssueEndTime(dto.getIssueEndTime());
//            coupon.setStatus(CouponStatus.UN_ISSUE);//如果不是立即发放，优惠券状态需要改为未发放
//            coupon.setTermDays(dto.getTermDays());
//            coupon.setTermBeginTime(dto.getTermBeginTime());
//            coupon.setTermEndTime(dto.getTermEndTime());
//        }
//        this.updateById(coupon);

        //方式2
        Coupon temp = BeanUtils.copyBean(dto, Coupon.class);
        if (isBeginIssue){
            temp.setStatus(CouponStatus.ISSUING);
            temp.setIssueBeginTime(now);
        }else {
            temp.setStatus(CouponStatus.UN_ISSUE);
        }
        this.updateById(temp);

        //如果优惠券是立即发放，将优惠券信息（优惠券id、领券开始时间结束时间、发行总数量、限领数量） 采用hash存入redis
        if(isBeginIssue){
            String key = PromotionConstants.COUPON_CACHE_KEY_PREFIX + id;//prs:coupon:优惠券id
            Map<String,String> map = new HashMap<>();
            map.put("issueBeginTime", String.valueOf(DateUtils.toEpochMilli(now)));
            map.put("issueEndTime", String.valueOf(DateUtils.toEpochMilli(dto.getIssueEndTime())));
            map.put("totalNum", String.valueOf(coupon.getTotalNum()));
            map.put("userLimit", String.valueOf(coupon.getUserLimit()));
            redisTemplate.opsForHash().putAll(key,map);
        }


        //如果优惠券的 领取方式 为指定发放 并且 优惠券之前的状态是待发放，需要生成兑换码
        if (coupon.getObtainWay() == ObtainType.ISSUE && coupon.getStatus() == CouponStatus.DRAFT){
            //兑换码兑换的截止时间，就是优惠券领取的截止时间；该时间是从前端传的封装到temp中了
            coupon.setIssueEndTime(temp.getIssueEndTime());
            exchangeCodeService.asyncGenerateExchangeCode(coupon);//异步生成兑换码
        }


    }

    /**
     * @description: 查询正在发放中的优惠券 
     * @params: []       
     * @return: 
     * @author: ajie
     * @date: 2024/5/23 13:35
     */
    @Override
    public List<CouponVO> queryIssuingCoupon() {
        //查询db coupon 条件：发放中 手动领取
        List<Coupon> couponList = this.lambdaQuery()
                .eq(Coupon::getStatus, CouponStatus.ISSUING)
                .eq(Coupon::getObtainWay, ObtainType.PUBLIC)
                .list();
        if (CollUtils.isEmpty(couponList)){
            return CollUtils.emptyList();
        }

        //查询用户券表user_coupon 条件当前用户 发放中的优惠券id
        //正在发放中的优惠券id集合
        Set<Long> couponIds = couponList.stream().map(Coupon::getId).collect(Collectors.toSet());
        //当前用户，针对正在发放中的优惠券领取记录
        List<UserCoupon> list = userCouponService.lambdaQuery()
                .eq(UserCoupon::getUserId, UserContext.getUser())
                .in(UserCoupon::getCouponId, couponIds)
                .list();
        //101:2  102:1
//        Map<Long, Long> issueMap = new HashMap<>();//代表当前用户针对每一个券 领取数量
//        for (UserCoupon userCoupon : list) {
//            Long num = issueMap.get(userCoupon.getCouponId());//优惠券领券数量
//            if (num == null){
//                issueMap.put(userCoupon.getCouponId(), 1L);
//            }else {
//                issueMap.put(userCoupon.getCouponId(), Long.valueOf(num.intValue()+1));
//            }
//        }
        //统计当前用户 针对每一个券 的已领取数量
        Map<Long, Long> issueMap = list.stream()
                .collect(Collectors.groupingBy(UserCoupon::getCouponId, Collectors.counting()));

        //统计当前用户 针对每一个券的 已领且未使用的数量
        Map<Long, Long> unuseMap = list.stream()
                .filter(c -> c.getStatus() == UserCouponStatus.UNUSED)
                .collect(Collectors.groupingBy(UserCoupon::getCouponId, Collectors.counting()));


        //po转vo返回
        List<CouponVO> voList = new ArrayList<>();
        for (Coupon coupon : couponList) {
            CouponVO vo = BeanUtils.copyBean(coupon, CouponVO.class);
            //优惠券还有剩余（issue_num < total_num） 且（统计用户券表user_coupon 取出当前用户已领数量 < user_limit）
            Long issNum = issueMap.getOrDefault(coupon.getId(), 0L);
            boolean available = coupon.getIssueNum() < coupon.getTotalNum() && issNum < coupon.getUserLimit();
            vo.setAvailable(available);//是否可领取
            //统计用户券表user_coupon取出当前用户已经领取且未使用的券的数量
            boolean received = unuseMap.getOrDefault(coupon.getId(),0L) > 0;
            vo.setReceived(received);//是否可使用
            voList.add(vo);
        }
        return voList;
    }
}
